from functools import wraps
from inspect import getgeneratorstate
from collections import namedtuple
import queue


def simple_coroutine():
    print('-> coroutine started')
    # 执行到此暂停, 调用端通过send()发送数据, 赋给x,
    # send()完之后yield计算出表达式的值, 然后协程恢复, 继续向下执行, 直到遇到下一个yield或者终止
    x = yield
    print('-> coroutine received: ', x)


def simple_coro2(a):
    print('-> started: a = ', a)
    b = yield a   # 预激产出a, 接收b, 产出a+b, 再接收c运行结束
    print('-> received: b = ', b)
    c = yield a + b
    print('-> received: c = ', c)


def averager():
    """
    averager 不停计算平均值的示例
    """
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total / count


class DemoException(Exception):
    """
    DemoException Demo Exception
    """


def coroutine(func):
    """
    coroutine 预激协程的装饰器
    """
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        # 预激
        next(gen)
        return gen
    return primer


def demo_exc_handling():
    """
    demo_exc_handling 异常处理示例
    """
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException:
            print('***DemoException handled. Continuing...')
        else:
            print('-> coroutine received: {!r}'.format(x))
    raise RuntimeError('This line should never run.')


def demo_finally():
    print('-> coroutine started')
    try:
        while True:
            try:
                x = yield
            except DemoException:
                print('***DemoException handled. Continuing...')
            else:
                print('-> coroutine received: {!r}'.format(x))
    finally:
        # do cleanup things
        print('-> coroutine ending')


Result = namedtuple('Result', 'count average')


# 子生成器
def averager_with_result():
    """
    averager_with_result 具有返回值的一协程

    Returns:
        [type]: 返回值
    """
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total / count
    return Result(count, average)


# 委派生成器
def grouper(results, key):
    while True:
        # grouper发送的每个值都有yield from处理,
        # 经由管道传给averager实例, 然后等待averager处理完成
        results[key] = yield from averager_with_result()


data = {
    'girls;kg':
        [40.9, 41.0],
    'girls;m':
        [1.6, 1.51, 1.4],
    'boys;kg':
        [39.0, 40.8, 43.2],
    'boys;m':
        [1.38, 1.5, 1.32]
}


def main():
    results = {}
    for key, values in data.items():
        group = grouper(results, key)
        # 预激group协程
        next(group)
        for value in values:
            # 把各个value传送给groupr, 传入的值最终到达averager中的term = yield
            group.send(value)
        # 发送None使得子生成器停止
        group.send(None)

    report(results)

def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))

# 时间, proc进程编号, 描述活动
Event = namedtuple('Event', 'time proc action')

def taxi_process(ident, trips, start_time=0):
    time = yield Event(start_time, ident, 'leave garage')
    for i in range(trips):
        time = yield Event(time, ident, 'pick up passenger')
        time = yield Event(time, ident, 'drop off passenger')
    yield Event(time, ident, 'going home')


class Simulator:

    def __init__(self, procs_map) -> None:
        # 优先权队列, 先进先出
        self.events = queue.PriorityQueue()
        self.procs = procs_map

    def run(self, end_time):
        for _, proc in sorted(self.procs.items()):
            first_event = next(proc)
            self.events.put(first_event)

        sim_time = 0
        while sim_time < end_time:
            if self.events.empty():
                print('*** end of events ***')
                break
            current_event = self.events.get()

            sim_time, proc_id, previous_action = current_event

            print('taxi:', proc_id, proc_id * '  ', current_event)

            active_proc = self.procs[proc_id]
            next_time = sim_time + 5
            try:
                next_event = active_proc.send(next_time)
            except StopIteration:
                del self.procs[proc_id]
            else:
                self.events.put(next_event)
        else:
            msg = '*** end of simulation time: {} events pending ***'
            print(msg.format(self.events.qsize()))

taxis = {
    0: taxi_process(ident=0, trips=2, start_time=0),
    1: taxi_process(ident=1, trips=4, start_time=5),
    2: taxi_process(ident=2, trips=6, start_time=10)
}

if __name__ == "__main__":
    # cor = simple_coroutine()
    # 第一步执行到yield被称预激prime, 让协程执行到第一个yield表达式
    # next(cor)
    # cor.send(64)

    # demo_exc = demo_exc_handling()
    # demo_exc.send(10)
    # demo_exc.send(10)
    # 触发关闭操作
    # demo_exc.close()
    # demo_exc.throw(DemoException)
    # this line will failed, trigger RuntimeError
    # demo_exc.throw(ZeroDivisionError)
    main()

    sim = Simulator(taxis)